/**
 * @author george michael brower / http://georgemichaelbrower.com/
 */

function loadSVG(loc, success, fail) {
	var xmlhttp = new XMLHttpRequest();
	xmlhttp.onreadystatechange=function() {
		if (xmlhttp.readyState==4) {
			if (xmlhttp.status == 200) {
				if (xmlhttp.responseXML == null) {
					if (fail != undefined) fail.call(this, loc);
				} else {
					var node = xmlhttp.responseXML.getElementsByTagName("svg").item(0);
					var svg = new SVG(node);
					svg.filename = loc;
					success.call(this, svg, loc);
				}
			} else {
				if (fail != undefined) fail.call(this, loc);
			}
 
 		}	
	}
	xmlhttp.open("GET", loc, true);
	xmlhttp.send(null);
}

function createSVG(svgText) {
	var node = document.createElement("svg");
	node.innerHTML = svgText;
	return new SVG(node);
}

var SVG = function(node) {
	this.filename = "";
	
	this.children = [];
	
	// TODO interpret things other than pixels.
	
	var w = node.getAttribute("width");
	var h = node.getAttribute("height");
	
  	this.width = w == null ? 0 : parseFloat(w.replace("px", ""));
  	this.height = h == null ? 0 : parseFloat(h.replace("px", "")); 
  	
	for (var i = 0; i < node.childNodes.length; i++) {
	
	  if (!node.childNodes.item(i).getAttribute) continue;
	  
	  var toAdd;
	  if (node.childNodes.item(i).nodeName == "g") {
	  	  toAdd = new SVG(node.childNodes.item(i));	
	  } else {
		  toAdd = new Path(node.childNodes.item(i));
	  }
	  
	  this.children.push(toAdd);
	  
	}
	
	// Draws every path in this SVG to the specified context.
	this.draw = function(context) {
		for (var i = 0; i < this.children.length; i++) {
			this.children[i].draw(context);
		}
	};
	
	
}

var Path = function(element) {

	this.element = element;
	this.commands = commands(element);
	this.lineWidth = parseLineWidth(element);
  	this.strokeStyle = parseStrokeStyle(element);
  	this.fillStyle = parseFillStyle(element);

// for at
		this.totalLength = 0;
		this.lengths = [];
		this.tlengths = []
		

	var turtle = function() {
		this.x;
		this.y;
		this.x1;
		this.y1;
		this.x2;
		this.y2;
		this.reset = function() {
			this.x = this.y = this.x1 = this.y1 = this.x2 = this.y2 = 0;
		}
		this.reset();
	}
		
	// Draws this entire path to the specified context.
	this.draw = function(context) {
		this.style(context);
		context.beginPath();
		this.shape(context);
		this.end(context);
		
	};

	// Calls canvas shape methods such as moveTo(), lineTo(), bezierCurveTo() based on the commands in this path.
	this.shape = function(context) {
		for (var i = 0; i < this.commands.length; i++) {
			this.commands[i].shape(turtle, context);
		}
	};
	
	this.lerp = function(a,b,c,d,t) {
			var t1 = 1.0 - t;
			return a*t1*t1*t1 + 3*b*t*t1*t1 + 3*c*t*t*t1 + d*t*t*t;
		
	};
	
	this.at = function(t, c) {
		var rx, ry;
		if (this.lengths.length == 0) {
			this.calcLengths(c);
		}
		
		var tt = this.tlengths[0];
		var i = 0;
		
		while (t > tt) {
			i++;
			tt += this.tlengths[i];
		}
		
		pt =  tt - this.tlengths[i];
		

		
		var it = this.map(t, pt, tt, 0, 1);
		
		
		for (var j = 0; j <= i; j++) {
		this.commands[j].shape(turtle, c);
		}
		var px = turtle.x;
		var py = turtle.y;
		
		this.commands[i+1].shape(turtle, c);
		
		
				rx = this.lerp(px, turtle.x1, turtle.x2, turtle.x, it);
				ry = this.lerp(py, turtle.y1, turtle.y2, turtle.y, it);

		return {x:rx, y:ry};	
	};
	
	this.map = function(v, i1, i2, o1, o2) {
		return o1 + (o2 - o1) * ((v - i1) / (i2 - i1));
	}

	
	this.calcLengths = function(c) {
		var rx,ry;
		var prx, pry;
		
		// go through and get the length of the entire path
		// as well as the lengths of each indiv. path
		
		var curLength = 0;
	
		var lengthAccuracy = 0.001;
		
		this.commands[0].shape(turtle, c);

		var px = prx = turtle.x;
		var py = pry = turtle.y;

		for (var i = 1; i < this.commands.length; i++) {
		
			curLength = 0;
	
				px = turtle.x;
				py = turtle.y;
				this.commands[i].shape(turtle, c);			
				
			for (var tt = 0; tt <=1; tt+= lengthAccuracy) {
			
				rx = this.lerp(px, turtle.x1, turtle.x2, turtle.x, tt);
				ry = this.lerp(py, turtle.y1, turtle.y2, turtle.y, tt);


				curLength += this.dist(rx, ry, prx, pry);

				
				prx = rx;
				pry = ry;
				
			}
			
			this.lengths.push(curLength);
			this.totalLength += curLength;
			
			
		}
		
		for (var j = 0; j < this.lengths.length; j++) {
			this.tlengths.push(this.lengths[j]/this.totalLength);
		}
	}
	
	this.dist = function (x, y, xx, yy) {
  	  return Math.sqrt((x - xx) * (x - xx) + (y - yy) * (y - yy));
	};
	
	// Sets the drawing style of the canvas context based on the styles in this Path.
	this.style = function(context) {
		
		if (this.lineWidth != null) {
		  context.lineWidth = this.lineWidth;
		} 
		
		if (this.strokeStyle != null) {
		  context.strokeStyle = this.strokeStyle;
		  if (this.lineWidth == undefined) {
        	context.lineWidth = 1;
		  }
		}
		
		if (this.fillStyle != null) {
		  context.fillStyle = this.fillStyle;
		}
		
	};
	
	// Calls context.fill() and/or context.stroke() depending on the styles in this Path.
	this.end = function(context) {
		if (this.fillStyle != null) context.fill();
		if (this.strokeStyle != null) context.stroke();
	}

}

var parseLineWidth = function(element) {
  var a = element.attributes.getNamedItem("stroke-width");
  return a == null ? null : parseFloat(a.nodeValue);
}

var parseStrokeStyle = function(element) {
   var a = element.attributes.getNamedItem("stroke");
   return a == null ? null : a.nodeValue;
}

var parseFillStyle = function(element) {
  var a = element.attributes.getNamedItem("fill");
  if (a == null) { 
	   var s = element.attributes.getNamedItem("stroke");
	   if (s != null) {
	   	return null;
	   	} else { 
	   	return "#000000";
	   	}
	} else { 
		if (a.nodeValue == "none") return null;
		return a.nodeValue;
	}
}

var Command = function(type, data) {
	this.type = type;
	this.data = data;
	this.debug = false;
	
	// Calls context shape methods such as moveTo(), lineTo(), bezierCurveTo(), etc.
	this.shape = function(turtle, c) {
	
		var px = turtle.x;
		var py = turtle.y;
		
		if (this.type == "M") {
		
			turtle.x = this.data[0];
			turtle.y = this.data[1];
			if (c) c.moveTo(turtle.x, turtle.y);

		
		} else if (this.type == "C") {
		
			turtle.x = this.data[4];
			turtle.y = this.data[5];
			if (c) c.bezierCurveTo(turtle.x1 = this.data[0],
							turtle.y1 = this.data[1], 
							turtle.x2 = this.data[2], 
							turtle.y2 = this.data[3], 
							turtle.x, 
							turtle.y);
		
		} else if (this.type == "c") {
		
			if (c) c.bezierCurveTo(turtle.x1 = turtle.x+this.data[0], 
							turtle.y1 = turtle.y+this.data[1], 
							turtle.x2 = turtle.x+this.data[2], 
							turtle.y2 = turtle.y+this.data[3], 
							turtle.x += this.data[4], 
							turtle.y += this.data[5]);
		
		} else if (this.type == "S") {
			
			turtle.x = this.data[2];
			turtle.y = this.data[3];
			var dx = turtle.x - turtle.x2;
			var dy = turtle.y - turtle.y2;
			if (c) c.bezierCurveTo(turtle.x1 = turtle.x+dx, 
							turtle.y1 = turtle.y+dy, 
							turtle.x2 = this.data[0], 
							turtle.y2 = this.data[1], 
							turtle.x, 
							turtle.y);
		
		} else if (this.type == "s") {
		
			var dx = turtle.x - turtle.x2;
			var dy = turtle.y - turtle.y2;
			if (c) c.bezierCurveTo(turtle.x1 = turtle.x+dx, 
							turtle.y1 = turtle.y+dy, 
							turtle.x2 = turtle.x+this.data[0], 
							turtle.y2 = turtle.y+this.data[1], 
							turtle.x += this.data[2], 
							turtle.y += this.data[3]);
		
		} else if (this.type == "L") {
		
			turtle.x1 = turtle.x;
			turtle.y1 = turtle.y;
			if (c) c.lineTo(turtle.x = this.data[0], 
					 turtle.y = this.data[1]);
			turtle.x2 = turtle.x;
			turtle.y2 = turtle.y;
		
		} else if (this.type == "l") {
	
			turtle.x1 = turtle.x;
			turtle.y1 = turtle.y;
			if (c) c.lineTo(turtle.x+=this.data[0], turtle.y+=this.data[1]);
			turtle.x2 = turtle.x;
			turtle.y2 = turtle.y;
			
		} else if (this.type == "H") {
		
			turtle.x1 = turtle.x;
			turtle.y1 = turtle.y;
			if (c) c.lineTo(turtle.x = this.data[0], turtle.y)
			turtle.x2 = turtle.x;
			turtle.y2 = turtle.y;
			
		} else if (this.type == "h") {
		
			turtle.x1 = turtle.x;
			turtle.y1 = turtle.y;
			if (c) c.lineTo(turtle.x += this.data[0], turtle.y)     
			turtle.x2 = turtle.x;
			turtle.y2 = turtle.y;
			
		} else if (this.type == "V") {
		
			turtle.x1 = turtle.x;
			turtle.y1 = turtle.y;
			if (c) c.lineTo(turtle.x, turtle.y = this.data[0]);
			turtle.x2 = turtle.x;
			turtle.y2 = turtle.y;
		
		} else if (this.type == "v") {
		
			turtle.x1 = turtle.x;
			turtle.y1 = turtle.y;
			if (c) c.lineTo(turtle.x, turtle.y += this.data[0]); 
			turtle.x2 = turtle.x;
			turtle.y2 = turtle.y;
		
		} else if (this.type == "z") {
			
			c.closePath();
			
		} else {
		
			alert("unrecognized command " + this.type);
			
		}
		
		if (c){ 
		c.strokeStyle = "#000000";
		c.lineWidth = 1;
		if (this.debug) {
			c.strokeRect(turtle.x-1.5, turtle.y-1.5, 3, 3);
			c.beginPath();
			c.moveTo(turtle.px, turtle.py);
			c.lineTo(turtle.x1, turtle.y1);
			c.closePath();
			c.stroke();
		}
		}
	
	}
	
}

// Utility functions

var commands = function(element) {
	
	if (element.nodeName.toLowerCase() == "path") {
		return commandsFromD(element.getAttribute("d"));
	}
		
	if (element.nodeName.toLowerCase() == "polygon") {
		return commandsFromPoints(element.getAttribute("points"));
	}
	
	if (element.nodeName.toLowerCase() == "line") {
		return commandsFromLine(element);
	}
	
	if (element.nodeName.toLowerCase() == "rect") {
		return commandsFromRect(element);
	}
	
	return [];
	
}

// Returns an array of commands as interpreted by the "d" attribute of a path.
var commandsFromD = function(d) {
	
	var toReturn = [];
	var commands = d.match(/[a-zA-Z][0-9\.\-\,]+/g);
	
	for (var i = 0; i < commands.length; i++) {
	
	var type = commands[i].charAt(0);
	
	// Dirty time.
	var commandData = commands[i].substr(1);
	commandData = commandData.replace(/\-/g, ",-")
	
	if (commandData.charAt(0) == ",") {
	commandData = commandData.substr(1);
	}
	commandData = commandData.split(",");
	for (var j = 0; j < commandData.length; j++) {
	 commandData[j] = parseFloat(commandData[j]);
	}
	
	toReturn.push(new Command(type, commandData));
	
	}
	
	return toReturn;
}

var commandsFromLine = function(element) {
	var toReturn = [];
	var x1 = parseFloat(element.getAttribute("x1"));
	var x2 = parseFloat(element.getAttribute("x2"));
	var y1 = parseFloat(element.getAttribute("y1"));
	var y2 = parseFloat(element.getAttribute("y2"));
	toReturn.push(new Command("M", [x1,y1]));
	toReturn.push(new Command("L", [x2,y2]));
	return toReturn;
}

// Returns an array of commands as interpreted by the "points" attribute of a polygon.
var commandsFromPoints = function(pointAttribute) {
	//pointAttribute = pointAttribute.replace(/\,\-/g, "-");
	
	var shouldBeComma = true;
	if (pointAttribute.indexOf(",") == -1) {
		for (var i = 0; i < pointAttribute.length; i++) {
			var c = pointAttribute.charAt(i);
			if (c == " ") {
				if (shouldBeComma) {
					pointAttribute = pointAttribute.setCharAt(i, ",");
				}
				shouldBeComma = !shouldBeComma;
			}
		}
	}
	
	pointAttribute = "M"+pointAttribute;
	pointAttribute = pointAttribute.replace(/ /g, "L") + "z";
	var toReturn = commandsFromD(pointAttribute);
	return toReturn;
}

String.prototype.setCharAt = function(index,chr) {
	if(index > this.length-1) return str;
	return this.substr(0,index) + chr + this.substr(index+1);
}

var commandsFromRect = function(element) {
	
	var toReturn = [];
	var x = parseFloat(element.getAttribute("x"));
	var y = parseFloat(element.getAttribute("y"));
	var w = parseFloat(element.getAttribute("width"));
	var h = parseFloat(element.getAttribute("height"));
	toReturn.push(new Command("M", [x,y]));
	toReturn.push(new Command("h", [w]));
	toReturn.push(new Command("v", [h]));
	toReturn.push(new Command("h", [-w]));
	toReturn.push(new Command("v", [-h]));
	return toReturn;
}